package npr;

import npr.gui.*;
import java.nio.*;
import processing.core.*;
import java.awt.event.MouseEvent;
import java.awt.event.KeyEvent;
import java.util.StringTokenizer;


public class LookUpTable extends GUI implements PConstants{
 
    private static PApplet parent;
    private AttenuationMap map;
    private AttenuationPanel panel;
    private PImage floppy;
    private SaveButton saveButton;

    /**
     * Creates a look-up table with the specified position.
     *
     * @param xpos horizontal position of the look-up table
     * @param ypos vertical position of the look-up table
     */
    public LookUpTable(int xpos, int ypos){

	this("", xpos, ypos);
    }

    /**
     * Creates a look-up table with the specified position from a file. The file extension is .lut.
     *
     * @param file the file name the look-up table should be loaded from
     * @param xpos horizontal position of the look-up table
     * @param ypos vertical position of the look-up table
     */
    public LookUpTable(String file, int xpos, int ypos){

	this(file, xpos, ypos, 22, null, defaultFontSize);
    }

    /**
     * Creates a look-up table with the specified position from a file. The file extension is .lut.
     * Uses specified font and font size for display.
     *
     * @param file the file name the look-up table should be loaded from
     * @param xpos horizontal position of the look-up table
     * @param ypos vertical position of the look-up table
     * @param height the vertical size of the look-up table
     * @param font the font to be used
     * @param fontSize the font size to be used 
     */
    public LookUpTable(String file, int xpos, int ypos, int height, PFont font, int fontSize){
	
	super(xpos, ypos, 256, height, font, fontSize);
   	int pos = file.indexOf(".");
       	if(pos != -1)
	    file = file.substring(0, pos);
        map= new AttenuationMap(file);
        parent.registerMouseEvent(this);
        parent.registerKeyEvent(this);
        floppy= parent.loadImage("floppy.gif");
        panel= new AttenuationPanel(xpos, ypos, 255, height);
        saveButton= new SaveButton(xpos+255+20, ypos, 150, 22, 20, floppy, this.font, file, defaultFontSize);
    }

    /**
     * Sets the parent applet. You don't have to call this. It is called automatically by the renderer.
     */
    public static void setParent(PApplet applet){
	parent= applet;
    }

    protected void mousePressed(int x, int y){
	panel.mousePressed(x, y);
	saveButton.mousePressed(x, y);
    }

    protected void rightClick(int x, int y){
	panel.rightClick(x, y);
    }

    protected void mouseDragged(int x, int y){
	panel.mouseDragged(x, y);
    }

    protected void mouseReleased(int x, int y){
	panel.mouseReleased(x, y);	
    }

    protected void mouseMoved(int x, int y){
	panel.mouseMoved(x, y);	
    }

    protected void keyPressed(char keyChar, int keyCode){
	panel.keyPressed(keyChar, keyCode);
	saveButton.keyPressed(keyChar, keyCode);
    }
    /*
      public void mouseEvent(MouseEvent event) {
      
      switch (event.getID()) {
      case MouseEvent.MOUSE_PRESSED:
      if(event.getButton()==MouseEvent.BUTTON1){
      panel.mousePressed(); 
      }
      else panel.rightKlick();
      saveButton.mousePressed();
      break;
      case MouseEvent.MOUSE_DRAGGED:
      panel.mouseDragged();
      break;
      case MouseEvent.MOUSE_RELEASED:
      panel.mouseReleased();
      break;
      case MouseEvent.MOUSE_MOVED:
      panel.mouseMoved();
      break;
      default:
      break;
      }
      }


      public void keyEvent(KeyEvent event) {
      
      switch (event.getID()) {
      case KeyEvent.KEY_TYPED:
      panel.keyPressed();
      saveButton.keyPressed();
      break;
      case KeyEvent.KEY_PRESSED:
      if(event.getKeyChar()==CODED){
      panel.keyPressed();
      saveButton.keyPressed();
      }
      break;
      default:
      break;
      }
      }
    */
    /** 
     * Saves a look-up table to a file. The file extension is .lut.
     * 
     * @param file the file name
     */
    public void save(String file){
	map.save(file);
    }
  
    /**
     * Sets a segment of the lookup table to a gray value.
     *
     * @param start the start of the segment. Accepted values are 0 to 255.
     * @param end the end of the segment. Accepted values are 0 to 255.
     * @param value the gray value of the segment. Accepted values are 0 to 255.
     */
    public void setRange(int start, int end, int value){
        if(start<0 || end>255){
            System.err.println("setRange: start and range have to be between 0 and 255");
        }
        else if(value<0 || value>255){
            System.err.println("setRange: value has to be between 0 and 255");
        }    
	else{
            map.fill(start, end, (short)value);
        }
    
    }

    
    /**
     * Sets a segment of the look-up table to a gradient. 
     * The start and end gray values of the segment are determined from the last gray value 
     * of the segment to the left and the first gray value of the segment to the right respectively. 
     *
     * @param start the start of the segment. Accepted values are 0 to 255.
     * @param end the end of the segment. Accepted values are 0 to 255.
     */
    public void setGradient(int start, int end){
        if(start<0 || end>255){
            System.err.println("setRange: start and range have to be between 0 and 255");
        }
        else{
            map.gradient(start, end);
        }
    }


    protected ShortBuffer get(){

        return map.buffer;
    }

    /**
     * Enables a previously disabled lookup-table for interactive use.
     */
    public void enable(){
	panel.enable();
	saveButton.enable();
    }
    
    /**
     * Disables a look-up table. A disabled look-up table will be drawn in gray and will not react to mouse input.
     * Useful in conjunction with a GUI when shading is disabled in the GUI.
     */
    public void disable(){
	panel.disable();
	saveButton.disable();
    }

    /** 
     * Draws a look-up table to the screen for interactive use.
     */
    public void draw(){

	if(display){
	    parent.hint(DISABLE_DEPTH_TEST);
	    panel.draw();
	    saveButton.draw();
	    parent.hint(ENABLE_DEPTH_TEST);
	}
    }
  

    private class AttenuationPanel{

	private int xpos, ypos;
	private int width, height;
        Popup popup;
        boolean move= false;
        boolean drawPopup= false;
        int from;
	boolean enabled= true;

        public AttenuationPanel(int xpos, int ypos, int width, int height){
	    this.xpos= xpos;
	    this.ypos= ypos;
	    this.width= width;
	    this.height= height;
        }


        private class Popup{
        
            private int xpos, ypos;
            private int width, height;
            private short value;
            private TextInputField fillField;

            public Popup(int xpos, int ypos, int width, int height){
                this.xpos= xpos;
                this.ypos= ypos;
		this.width= width;
		this.height= height;
		value= map.get(xpos-panel.xpos);
		fillField= new TextInputField(xpos-width/2, ypos, width, height/2, font, 14, FloatField.toString(value, 0), "", "");
	     
            }

	
            public boolean gradientSelected(){
		return(parent.mouseX>=xpos-width/2 && parent.mouseX<=xpos+width/2 && parent.mouseY>=ypos-height/2 && parent.mouseY<=ypos);
            }


            public boolean fillSelected(){
             
		return fillField.hasInput();
            }


            public short getValue(){
		if(fillField.hasInput()){
		    try{
			short newValue= Short.parseShort(fillField.getInput());
			if(newValue<0){
			    fillField.setInput(FloatField.toString(0f, 0));
			    value= 0;
			}
			else if(newValue>255){
			    fillField.setInput(FloatField.toString(255f, 0));
			    value= 255;

			}
			else{
			    value= newValue;
			}
		    }
		    catch(NumberFormatException e){
			return value;
		    }
		}
		return value;
	    }


            public void draw(){
                for(int i= 1; i<=width; i++){
                    parent.stroke((i)*(256/width));
                    parent.line(xpos-width/2+i, ypos-height/2, xpos-width/2+i, ypos);
                }
		
                fillField.draw();
                parent.stroke(0);
                parent.noFill();
                parent.rect(xpos-width/2, ypos-height/2, width, height);
            }
        

	    protected boolean inside(){
		return(parent.mouseX>=xpos-width/2 && parent.mouseX<=xpos+width/2 && parent.mouseY>=ypos-height/2 && parent.mouseY-height/2<=ypos+height/2);
	    }
	}

	protected boolean inside(){
	    return(parent.mouseX>=xpos && parent.mouseX<=xpos+255 && parent.mouseY>=ypos && parent.mouseY<=ypos+height);
	}


	public void enable(){
	    enabled= true;
	}


	public void disable(){
	    enabled= false;
	}

	public void mousePressed(int x, int y){

	    if(inside()&&!drawPopup){
		int index= x-xpos;
		//can't move first Marker
		if(index==0) index= 2;
		//can't move last Marker
		if(index==255) index= 253;
            
		if(!map.marked(index) && !map.marked(index-1) && !map.marked(index+1)){
		    map.insert(index);
		}
		if(map.marked(index)){
		    move= true;
		    from= index;
		}
		else if(map.marked(index-1)){
		    move= true;
		    from= index-1;
		}
		else if(map.marked(index+1)){
		    move= true;
		    from= index+1;
		}
	    }
	    else{
		if(drawPopup){
		    if(popup.gradientSelected()){
                
			map.gradient(map.findPrevious(popup.xpos-xpos), map.findNext(popup.xpos-xpos));
			drawPopup= false;
		    }
		    else drawPopup= popup.inside();
		}
	    }
	}
 

        public void mouseDragged(int x, int y){
            
            if(move && !drawPopup){
                int to= parent.mouseX-xpos;
                if(parent.mouseY<ypos || parent.mouseY>ypos+height){
                    move= false;
                    map.delete(from);
                }
                else{
                    if(to<0)to= 0;
                    else if(to>255) to= 255;
                    map.move(from, to);
                    from= to;
                }
                if(to==0 || to==255) move= false;
            }
        }


        public void mouseMoved(int x, int y){
            if(drawPopup && !popup.inside()){
                drawPopup= false;
            }
        }


        public void rightClick(int x, int y){
            if(inside() && !map.marked(parent.mouseX-xpos)){
		popup= new Popup(x, y, 32, 32);
		drawPopup= true;
		
            }
        }


	public void mouseReleased(int x, int y){
	    move= false;
	}


	public void keyPressed(char keyChar, int keyCode){
	
	}

	
	public void draw(){
	    if(drawPopup && popup.fillSelected()){
		map.fill(map.findPrevious(popup.xpos-xpos), map.findNext(popup.xpos-xpos), popup.getValue());
		drawPopup= false;
	    }
	    for(int i= 0; i<256; i++){
		if(map.marked(i))parent.stroke(fontColor);
		else{
		    if(enabled) parent.stroke(map.get(i));
		    else parent.stroke(map.get(i)+(255.0f-map.get(i))/2.0f);
		} 
		parent.line(xpos+i, ypos, xpos+i, ypos+height);
	    }
	    parent.stroke(fontColor);
	    parent.noFill();
	    parent.rect(xpos, ypos, 255, height); 
	    if (drawPopup) popup.draw();
	}
    }



    private class SaveButton{

    
	private TextInputField textField; 
	private PictureButton button;
	private boolean displayTextField= false;
	private boolean mousePressed= false, keyPressed= false;

	public SaveButton(int xpos, int ypos, int width, int height, int gap, PImage icon, PFont font, String file, int fontSize){
	   
	    button= new PictureButton(xpos, ypos, height, height, icon, icon);
	    textField= new TextInputField(xpos+height+gap, ypos, width, height, font, fontSize, file, "", ".lut");
	}

	public void enable(){
	    button.enable();
	}
    

	public void disable(){
	    button.disable();
	}

	protected void mousePressed(int x, int y){

	    mousePressed= true;
	}

	protected void keyPressed(char keyChar, int key){
	  
	    keyPressed= true;
	}


	public void draw(){
	    if(mousePressed){
		if(displayTextField){
		    displayTextField= textField.isActive();
		}
		else{
		    displayTextField= button.isActive();	
		}
		mousePressed= false;
	    }
	    if(keyPressed && displayTextField){
		if(textField.hasInput()){
		    save(parent.dataPath(textField.getInput()+".lut"));
		    displayTextField= false;
		}
		keyPressed= false;
	    }
	    button.draw();
	    if(displayTextField){
		textField.draw();
	    }
	}
    }


    private class AttenuationMap{

	private class Marker{
	    protected int position;
	    protected Marker next;
	    protected Marker previous;
	    protected boolean left;
	    protected boolean right;          

	    protected Marker(int i){
		position= i;
	    }
	}
      
	private Marker[] markers;
	protected ShortBuffer buffer;

	private boolean GRADIENT= true;
	private boolean FILL= false;

	public AttenuationMap(String file){
	    markers= new Marker[256];
	    buffer= ShortBuffer.allocate(256);
	    if(file==null || file.compareTo("")==0){
                Marker startMarker= markers[0]= new Marker(0);		
		Marker endMarker= markers[255]= new Marker(255);
		startMarker.next=endMarker;
		endMarker.previous=startMarker;
		buffer.put(0, (short)0);
		buffer.put(255, (short)255);
		gradient(0, 255);
	    }
	    else{
                int position= 0;
                Marker startMarker= markers[0]= new Marker(0);
                String[] sections= parent.loadStrings(parent.dataPath(file + ".lut"));
                for(int i=0; i<sections.length; i++){
                    int start= position;
                    boolean fill= true;
                    StringTokenizer st = new StringTokenizer(sections[i]);
                    while (st.hasMoreTokens()) {
                        buffer.put(position, Short.parseShort(st.nextToken()));
                        if(buffer.get(position)!=buffer.get(start))fill= false;
                        if(position<255)position++;
                    }
                    markers[position]= new Marker(position);
                    markers[position].previous= startMarker;
                    startMarker.next= markers[position];
                    if(fill)
                        markers[position].left= startMarker.right= FILL;
                    else
                        markers[position].left= startMarker.right= GRADIENT;
                    startMarker= markers[position];
                }	
	    }
	}

	public void save(String file){
	    int numMarkers= 0;
	    for(int i=0; i<256; i++){
		if(markers[i]!=null) numMarkers++;
	    }
	    String sections[]= new String[numMarkers-1];
	    Marker start= markers[0], end;
	    for(int i=0; i<numMarkers-1; i++){
		end= start.next;
		sections[i]= new String();
		for(int j=start.position; j<end.position; j++){
		    sections[i]= sections[i].concat(Short.toString(buffer.get(j))+ " ");
		}
      		start= end;
	    }
	    sections[numMarkers-2]= sections[numMarkers-2].concat(Short.toString(buffer.get(255)));;
	    parent.saveStrings(file, sections);
	}

	public short get(int i){
	    return buffer.get(i);
	}

	private void delete(int start, int end){
      
	    Marker startMarker= markers[start]; 
	    Marker endMarker= markers[end];
	    startMarker.next= endMarker;
	    endMarker.previous= startMarker;
	    for(int i= start+1; i<end; i++){
		markers[i]= null;
	    }
	}

	public void delete(int i){
	    Marker toDelete= markers[i];
	    if(toDelete!=null && i!=0 && i!=255){
		Marker previousMarker= toDelete.previous;
		Marker nextMarker= toDelete.next;
		previousMarker.next= nextMarker;
		nextMarker.previous= previousMarker;
		markers[i]= null;
		int start= previousMarker.position;
		int end= nextMarker.position;
		if(previousMarker.right==GRADIENT || nextMarker.left==GRADIENT){
		    gradient(start, end);
		}
		else{
		    short startValue= buffer.get(start);
		    short endValue= buffer.get(end);
		    short newValue= (short)(startValue+(endValue-startValue)/2);
		    fill(start, end, newValue);
		}
	    }
	}

	public void insert(int i){
	    if(markers[i]==null){
		Marker previousMarker= markers[findPrevious(i)];
		Marker nextMarker= previousMarker.next;
		Marker newMarker= markers[i]= new Marker(i);
		newMarker.previous= previousMarker;
		newMarker.next= nextMarker;
		newMarker.left= previousMarker.right;
		newMarker.right= nextMarker.left;
		previousMarker.next= newMarker;
		nextMarker.previous= newMarker;
	    }
	}

	public void move(int from, int to){
	    Marker toMove= markers[from];
	    if(toMove!=null){
		short previousValue= buffer.get(from);
		Marker nextMarker= toMove.next;
		Marker previousMarker= toMove.previous;
		if(nextMarker.position<=to || previousMarker.position>=to){
		    delete(from);
		}
		else{
		    markers[from]= null;
		    markers[to]= toMove;
		    toMove.position= to;
		    buffer.put(to, previousValue);
		    if(toMove.right==GRADIENT){
			gradient(to, nextMarker.position);
		    }
		    else{
			fill(to, nextMarker.position, buffer.get(nextMarker.position-1));
		    }
		    if(toMove.left==GRADIENT){
			gradient(previousMarker.position, to);
		    }
		    else{
			fill(previousMarker.position, to, buffer.get(previousMarker.position));
		    }
		}
	    }
	}
    
	protected int findPrevious(int i){
	    while(markers[i]==null){
		i--;
	    }
	    return i;
	}

	protected int findNext(int i){
	    while(markers[i]==null){
		i++;
	    }
	    return i;
	}

	public boolean marked(int i){
	    return markers[i]!=null;
	}

	public void gradient(int start, int end){
	    insert(start);
	    insert(end);
	    Marker startMarker= markers[start];
	    Marker endMarker= markers[end];
	    delete(start, end);
	    startMarker.right= GRADIENT;
	    endMarker.left= GRADIENT;
	    short startColor= start==0?buffer.get(0):buffer.get(start-1);
	    short endColor= buffer.get(end);
	    float step= (float)(endColor-startColor)/(float)(end-start);
	    for(int i= start; i<end; i++){
		buffer.put(i, (short)(startColor+(short)(step*(float)(i-start))));
	    }
	}

	public void fill(int start, int end, short value){
	    insert(start);
	    insert(end);
	    Marker startMarker= markers[start];
	    Marker endMarker= markers[end];
	    delete(start, end);
	    startMarker.right= FILL;
	    endMarker.left= FILL;
	    for(int i= start; i<end; i++){
		buffer.put(i, value);
	    }
	    if(endMarker.next==null)buffer.put(end, value);
	    if(startMarker.previous!=null && startMarker.left==GRADIENT){
		gradient(startMarker.previous.position, startMarker.position);
	    }
	    if(endMarker.next!=null && endMarker.right==GRADIENT){
		gradient(endMarker.position, endMarker.next.position);
	    }
	}
    }
}

